/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.erickpardal.game.fpsdemo.scene;

import java.text.DecimalFormat;

import com.jme.animation.SpatialTransformer;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.light.PointLight;
import com.jme.light.SimpleLightNode;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.TriMesh;
import com.jme.scene.Skybox;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Disk;
import com.jme.scene.shape.Dome;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.RenderState.StateType;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmex.effects.LensFlare;
import com.jmex.effects.LensFlareFactory;

/**
 * SkyDome provides a dynamic sky environment. It implements dynamic sun, moon, clouds
 * motion and star effect. The textures applied to the dome, the plane and the star effect
 * are preloaded to ensure smoothness of game.
 *
 * @author Yi Wang (Neakor)
 */
public class SkyDome extends Node{
	// SerialVersion.
	private static final long serialVersionUID = 1L;
	// The Renderer object.
	private Renderer renderer;
	// The radius of the sky dome.
	private float radius;
	// The time of one complete cycle.
	private float cycle;
	// The float array contains the numbers of the sunlight color at dawn and dusk.
	private float[] dColor;
	// The float array contains the numbers of the moonlight color during night time.
	private float[] mColor;
	// The astronomy LightState.
	private LightState astronomyLightState;
	// The astronomy system animator.
	private SpatialTransformer astronomyAnimator;
	// The sunlight object.
	private PointLight sunlight;
	// The sun's lens flare effect object.
	private LensFlare sunFlareEffect;
	// The moonlight object.
	private PointLight moonlight;
	// The moon's lens flare effect object.
	private LensFlare moonFlareEffect;
	// The star effect skybox.
	private Skybox starEffect;

	/**
	* No argument Constructor of SkyDome.
	*/
	public SkyDome() {
		this(1000, (Texture)null, (Texture)null, (Texture)null, 7200, new float[]{0.9843f,0.5098f,0,1}, new float[]{0.2745f,0.3961f,0.6196f,1});
	}

	/**
	* Constructor of SkyDome.
     * @param cameraFar The far plane value of the camera.
	* @param model The model of the skydome including the dome modela and the plane model.
     * @param starTexture The texture used for the star effect.
     * @param cycle The time it takes the sun to complete one rotation cycle in seconds.
     * @param dColor The color of the sunlight at dawn and dusk in the order of RGBA.
     * @param mColor The color of the moonlight during the night time.
	*/
	public SkyDome(float cameraFar, Node model, Texture starTexture, float cycle, float[] dColor, float[] mColor) {
		// Store the informaion.
		this.renderer = DisplaySystem.getDisplaySystem().getRenderer();
		// The radius is 50% of the camera's far plane value.
		this.radius = cameraFar/2;
		this.cycle = cycle;
		this.dColor = dColor;
		this.mColor = mColor;
		// Initialize the sky dome.
		this.initializeSkyDome(model);
		// Initialize the astronomy system.
		this.initializeAstronomySystem();
		// Initialize the star effect.
		this.initializeStarEffect(starTexture);
		// Set up render states.
		this.setupRenderStates();
	}

	/**
	* Constructor of SkyDome.
     * @param cameraFar The far plane value of the camera.
     * @param domeTexture The texture used for the sky dome.
     * @param planeTexture The texture used for the plane under the sky dome.
     * @param starTexture The texture used for the star effect.
     * @param cycle The time it takes the sun to complete one rotation cycle in seconds.
     * @param dColor The color of the sunlight at dawn and dusk in the order of RGBA.
     * @param mColor The color of the moonlight during the night time.
	*/
	public SkyDome(float cameraFar, Texture domeTexture, Texture planeTexture, Texture starTexture, float cycle, float[] dColor, float[] mColor) {
		// Store the informaion.
		this.renderer = DisplaySystem.getDisplaySystem().getRenderer();
		// The radius is 50% of the camera's far plane value.
		this.radius = cameraFar/2;
		this.cycle = cycle;
		this.dColor = dColor;
		this.mColor = mColor;
		// Initialize the sky dome.
		this.initializeSkyDome(domeTexture, planeTexture);
		// Initialize the astronomy system.
		this.initializeAstronomySystem();
		// Initialize the star effect.
		this.initializeStarEffect(starTexture);
		// Set up render states.
		this.setupRenderStates();
	}

	/**
	* Update the sky dome.
	* @param cameraPosition The current position of the camera.
	*/
	public void update(Vector3f cameraPosition) {
		// Set the sky dome's local translation to the camera's current position.
		this.setLocalTranslation(cameraPosition.x, cameraPosition.y - this.radius/2, cameraPosition.z);
		// Get the current time.
		float currentTime = this.getCurrentTime(false);
		// Update the sunlight color based on the current time.
		this.updateSunLightColor(currentTime);
		// Update the moonlight color based on the current time.
		this.updateMoonLightColor(currentTime);
		// Update the sun's lens flare effect based on the current time.
		this.updateSunLensFlare(currentTime);
		// Update the moon's lens flare effect based on the current time.
		this.updateMoonLensFlare(currentTime);
		// Update the star effect based on the current time.
		this.updateStarEffect(currentTime);
	}

	/**
	* Change the sunlight color based on the given current time.
	* @param currentTime The current time.
	*/
	private void updateSunLightColor(float currentTime) {
		// Create an array defines the new sunlight color.
		float[] newColor = new float[4];
		// If the current time is between 6:00 and 12:00, change the color towards white.
		if(currentTime >= 6 && currentTime < 12)
		{
			for(int i = 0; i < newColor.length; i++)
			{
				newColor[i] = this.dColor[i]+((1-this.dColor[i])/6)*(currentTime-6);
			}
			this.sunlight.setDiffuse(new ColorRGBA(newColor[0], newColor[1], newColor[2], newColor[3]));
		}
		// Else if the current time is between 12:00 and 18:00, change the color towards dColor.
		else if(currentTime >= 12 && currentTime < 18)
		{
			for(int i = 0; i < newColor.length; i++)
			{
				newColor[i] = 1 - ((1-this.dColor[i])/6)*(currentTime-12);
			}
			this.sunlight.setDiffuse(new ColorRGBA(newColor[0], newColor[1], newColor[2], newColor[3]));
		}
		// Else if the current time is between 18:00 and 24:00, change the color towards black.
		else if(currentTime >= 18 && currentTime < 24)
		{
			for(int i = 0; i < newColor.length; i++)
			{
				newColor[i] = this.dColor[i] - (this.dColor[i]/6)*(currentTime - 18);
			}
			this.sunlight.setDiffuse(new ColorRGBA(newColor[0], newColor[1], newColor[2], newColor[3]));
		}
		// Else if the current time is between 0:00 and 6:00, change the color towards dColor.
		else if(currentTime >= 0 && currentTime < 6)
		{
			for(int i = 0; i < newColor.length; i++)
			{
				newColor[i] = (this.dColor[i]/6)*(currentTime - 0);
			}
			this.sunlight.setDiffuse(new ColorRGBA(newColor[0], newColor[1], newColor[2], newColor[3]));
		}
	}

	/**
	* Change the moonlight color based on the given current time.
	* @param currentTime The current time.
	*/
	private void updateMoonLightColor(float currentTime) {
		// Create an array defines the new moonlight color.
		float[] newColor = new float[4];
		// If the current time is between 6:00 and 12:00, change the moonlight color towards black.
		if(currentTime >= 6 && currentTime <= 12)
		{
			for(int i = 0; i < newColor.length; i++)
			{
				newColor[i] = this.mColor[i] - (this.mColor[i]/6)*(currentTime - 6);
			}
			this.moonlight.setDiffuse(new ColorRGBA(newColor[0], newColor[1], newColor[2], newColor[3]));
		}
		// Else if the current time is between 12:00 and 18:00, change the moonlight color towards the mColor.
		else if(currentTime > 12 && currentTime <= 18)
		{
			for(int i = 0; i < newColor.length; i++)
			{
				newColor[i] = (this.mColor[i]/6)*(currentTime - 12);
			}
			this.moonlight.setDiffuse(new ColorRGBA(newColor[0], newColor[1], newColor[2], newColor[3]));
		}
	}

	/**
	* Increase or decrease the sun's lens flare effect intensity based on the given current time.
	* The sun's maximum intensity is 1.
	* @param currentTime The current time.
	*/
	private void updateSunLensFlare(float currentTime) {
		// If the current time is between 8:00 and 12:00, increase the intensity.
		if(currentTime >= 8 && currentTime < 12)
		{
			this.sunFlareEffect.setIntensity((currentTime-8)/4);
		}
		// Else if the current time is between 12:00 and 16:00, decrease the intensity.
		else if(currentTime >= 12 && currentTime < 16)
		{
			this.sunFlareEffect.setIntensity(1-(currentTime-12)/4);
		}
		// Else if the current time is equal or greater than 16:00 or less than 8:00, disable the sun's lens flare.
		else if(currentTime >= 16 || currentTime < 8)
		{
			this.sunFlareEffect.setIntensity(0);
		}
	}

	/**
	* Increase or decrease the moon's lens flare effect intensity based on the given current time.
	* The moon's maximum intensity is 0.2.
	* @param currentTime The current time.
	*/
	private void updateMoonLensFlare(float currentTime) {
		// If the current time is between 20:00 and 24:00, increase the intensity.
		if(currentTime >= 20 && currentTime < 24)
		{
			this.moonFlareEffect.setIntensity(((currentTime-20)/4)/5);
		}
		// Else if the current time is between 0:00 and 4:00, decrease the intensity.
		else if(currentTime >= 0 && currentTime < 4)
		{
			this.moonFlareEffect.setIntensity((1-(currentTime-0)/4)/5);
		}
		// Else if the current time is equal or greater than 4:00 or less than 20:00, disable the lens flare.
		else if(currentTime >= 4 || currentTime < 20)
		{
			this.moonFlareEffect.setIntensity(0);
		}
	}

	/**
	* Increase or decrease the alpha of all sides of star effect skybox based on the given current time.
	* @param currentTime The current time.
	*/
	private void updateStarEffect(float currentTime) {
		// If the current time is between 20:00 and 24:00, increase the alpha of all sides toward 1.
		if(currentTime >= 20 && currentTime < 24)
		{
			this.starEffect.getFace(Skybox.Face.North).getDefaultColor().a = (currentTime - 20)/4;
			this.starEffect.getFace(Skybox.Face.East).getDefaultColor().a = (currentTime - 20)/4;
			this.starEffect.getFace(Skybox.Face.South).getDefaultColor().a = (currentTime - 20)/4;
			this.starEffect.getFace(Skybox.Face.West).getDefaultColor().a = (currentTime - 20)/4;
			this.starEffect.getFace(Skybox.Face.Up).getDefaultColor().a = (currentTime - 20)/4;
		}
		// Else if the current time is between 0:00 and 4:00, decrease the alpha of all sides toward 0.
		else if(currentTime >= 0 && currentTime < 4)
		{
			this.starEffect.getFace(Skybox.Face.North).getDefaultColor().a = 1 - (currentTime - 0)/4;
			this.starEffect.getFace(Skybox.Face.East).getDefaultColor().a = 1 - (currentTime - 0)/4;
			this.starEffect.getFace(Skybox.Face.South).getDefaultColor().a = 1 - (currentTime - 0)/4;
			this.starEffect.getFace(Skybox.Face.West).getDefaultColor().a = 1 - (currentTime - 0)/4;
			this.starEffect.getFace(Skybox.Face.Up).getDefaultColor().a = 1 - (currentTime - 0)/4;
		}
		// Else if the current time is equal or greater than 4:00 or less than 20:00, set the alpha to 0.
		else if(currentTime >= 4 || currentTime < 20)
		{
			this.starEffect.getFace(Skybox.Face.North).getDefaultColor().a = 0;
			this.starEffect.getFace(Skybox.Face.East).getDefaultColor().a = 0;
			this.starEffect.getFace(Skybox.Face.South).getDefaultColor().a = 0;
			this.starEffect.getFace(Skybox.Face.West).getDefaultColor().a = 0;
			this.starEffect.getFace(Skybox.Face.Up).getDefaultColor().a = 0;
		}
	}

	/**
	* Initialize the sky dome with the given model node.
	* @param model The model of the skydome including the dome modela and the plane model.
	*/
	private void initializeSkyDome(Node model) {
		// Set the position of the model.
		model.setLocalTranslation(new Vector3f(0,this.radius/2,0));
		model.setIsCollidable(false);
		// Build the clouds motion.
		this.buildCloudsAnimation(model);
		// Set the modes for the model.
		this.setModes(model);
		// Attach the model to the sky dome.
		this.attachChild(model);
	}

	/**
	* Initialize the sky dome which contains a dome and a plane.
     * @param domeTexture The texture used for the sky dome.
     * @param planeTexture The texture used for the plane under the sky dome.
	*/
	private void initializeSkyDome(Texture domeTexture, Texture planeTexture) {
		// Attach both dome, plane and star effect to a node.
		Node model = new Node("SkydomeModel");
		model.attachChild(this.buildDome(domeTexture));
		model.attachChild(this.buildPlane(planeTexture));
		// Build the clouds animation.
		this.buildCloudsAnimation(model);
		// Set the modes for the model node.
		this.setModes(model);
		// Attach the model to the sky dome.
		this.attachChild(model);
	}

	/**
	* Build the clouds animation which is a simple texture translation.
	* @param dome The dome of sky dome.
	*/
	private void buildCloudsAnimation(Spatial dome) {
		// Create the animator for the sky's clouds motion.
		SpatialTransformer animator = new SpatialTransformer(1);
		animator.setObject(dome, 0, -1);
	    Quaternion start = new Quaternion();
	    start.fromAngleAxis(0, new Vector3f(0,1,0));
	    animator.setRotation(0, 0, start);
	    Quaternion middle = new Quaternion();
	    middle.fromAngleAxis(FastMath.DEG_TO_RAD*180, new Vector3f(0,1,0));
	    animator.setRotation(0, this.cycle/30, middle);
	    Quaternion end = new Quaternion();
	    end.fromAngleAxis(FastMath.DEG_TO_RAD*360, new Vector3f(0,1,0));
	    animator.setRotation(0, this.cycle/15, end);
	    // Prepare the animator.
	    animator.setActive(true);
	    animator.setRepeatType(Controller.RT_WRAP);
	    animator.interpolateMissing();
		// Attach the animator to the sky.
		dome.addController(animator);
	}

	/**
	* Build the dome as the sky.
	* @param domeTexture The texture used for the sky dome.
	*/
	private Dome buildDome(Texture domeTexture) {
		// Create a dome as the sky.
		Dome dome = new Dome("Sky", new Vector3f(0,0,0), 64, 64, this.radius, true);
		dome.setLocalTranslation(new Vector3f(0,0,0));
		dome.setModelBound(new BoundingBox());
		dome.updateModelBound();
		dome.setIsCollidable(false);
		// Apply the given texture to the dome.
		TextureState ts = this.renderer.createTextureState();
		ts.setTexture(domeTexture);
		ts.setEnabled(true);
		ts.apply();
		dome.setRenderState(ts);
		// Set the modes.
		//this.setModes(dome);
		// Build the clouds motion.
		//this.buildCloudsAnimation(dome);
		// Attach the dome to the sky dome.
		//this.attachChild(dome);

        return dome;
	}

	/**
	* Build the plane under the sky dome.
	* @param planeTexture The texture used for the plane under the sky dome.
	*/
	private Disk buildPlane(Texture planeTexture) {
		// Create a disk as the plane under the sky dome.
		Disk plane = new Disk("Ground", 64, 64, 11*this.radius/10);
		plane.setLocalTranslation(new Vector3f(0,0,0));
		Quaternion rotation = new Quaternion();
		rotation.fromAngleAxis(270*FastMath.DEG_TO_RAD, new Vector3f(1,0,0));
		plane.setLocalRotation(rotation);
		plane.setModelBound(new BoundingBox());
		plane.updateModelBound();
		plane.setIsCollidable(false);
		// Apply the given texture to the plane.
		TextureState ts = this.renderer.createTextureState();
		ts.setTexture(planeTexture);
		ts.setEnabled(true);
		ts.apply();
		plane.setRenderState(ts);
		// Set the modes.
		//this.setModes(plane);
		// Attach the plane to the sky dome.
		//this.attachChild(plane);

        return plane;
	}

	/**
	* Initialize the astronomy system which includes a sun system and a moon system.
	*/
	private void initializeAstronomySystem() {
		// Create the astronomy center node.
		Node astronomyNode = new Node("AstronomyCenter");
		astronomyNode.setLocalTranslation(new Vector3f(0,0,0));
		// Create the LightState for both the sun and the moon.
		this.astronomyLightState = this.renderer.createLightState();
		this.astronomyLightState.setTwoSidedLighting(true);
		// Build the sun system.
		this.buildSunSystem(astronomyNode);
		// Build the moon system.
		this.buildMoonSystem(astronomyNode);
		// Build the astronomy rotational animation.
		this.buildAstronomyAnimation(astronomyNode);
		// Attach the astronomy node to the sky dome.
		this.attachChild(astronomyNode);
	}

	/**
	* Build the sun system which contains a point light outside the sky dome and a lens
	* flare effect inside the sky dome.
	* @param astronomyNode The astronomy center node.
	*/
	private void buildSunSystem(Node astronomyNode) {
		// Create the sun system node.
		Node sunSystem = new Node("SunSystem");
		// Build and attach the sun and the lens flare to the sun system node.
		sunSystem.attachChild(this.buildSun());
		sunSystem.attachChild(this.buildSunLensFlare());
		// Attach the sun system node to the astronomy node.
		astronomyNode.attachChild(sunSystem);
	}

	/**
	* Build the sun.
	* @return The light node contains the sun.
	*/
	private SimpleLightNode buildSun() {
		// Create the sunlight which is a PointLight.
		this.sunlight = new PointLight();
		// Initially the time is 0:00, so the sunlight color is black.
		this.sunlight.setEnabled(true);
		this.sunlight.setDiffuse(new ColorRGBA(0,0,0,0));
		this.sunlight.setAmbient(ColorRGBA.gray);
		this.sunlight.setAttenuate(false);
		this.sunlight.setShadowCaster(true);
		// Create the sun which is a LightNode.
		SimpleLightNode sun = new SimpleLightNode("Sun", this.sunlight);
		sun.setLocalTranslation(new Vector3f(0,-20*this.radius,0));
		// Attach the sunlight to the astronomy LightState.
		this.astronomyLightState.attach(this.sunlight);
		// Return the sun LightNode.
		return sun;
	}

	/**
	* Build the sun's lens flare effect.
	* @return The light node contains the sun's lens flare effect.
	*/
	private Node buildSunLensFlare() {
		// Create the sun lens flare light node.
		Node sunLensFlare = new Node("SunLensFlare");
		sunLensFlare.setLocalTranslation(new Vector3f(0,-95*this.radius/100,0));
		// Load the textures.
	    TextureState[] textureStates = new TextureState[4];
	    Texture[] texs = new Texture[4];
	    String directory = getClass().getClassLoader().getResource("com/erickpardal/game/fpsdemo/data/textures/scene/lensflare/").getPath();
	    for(int i = 0; i < textureStates.length; i++)
	    {
	        textureStates[i] = this.renderer.createTextureState();
	        int flareIndex = i + 1;
	        String fileName = directory + "flare" + flareIndex;
	        texs[i] = TextureManager.loadTexture(fileName, Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
	        textureStates[i].setTexture(texs[i]);
	        textureStates[i].setEnabled(true);
	        textureStates[i].apply();
	    }
	    // Create the sun's lens flare effect.
		this.sunFlareEffect = LensFlareFactory.createBasicLensFlare("LensFlareEffect", textureStates);
		// Initially the earth time is 0:00, so the intensity is 0.
		this.sunFlareEffect.setIntensity(0);
		this.sunFlareEffect.updateRenderState();
		// Attach the effect to the light node.
		sunLensFlare.attachChild(this.sunFlareEffect);
		// Return the sun's lens flare light node.
		return sunLensFlare;
	}

	/**
	* Build the moon system which contains a point light outside the sky dome and a lens
	* flare effect inside the sky dome.
	* @param astronomyNode The astronomy center node.
	*/
	private void buildMoonSystem(Node astronomyNode) {
		// Create the moon system node.
		Node moonSystem = new Node("MoonSystem");
		// Build and attach the moon and the lens flare to the moon system node.
		moonSystem.attachChild(this.buildMoon());
		moonSystem.attachChild(this.buildMoonLensFlare());
		// Attach the moon system node to the astronomy node.
		astronomyNode.attachChild(moonSystem);
	}

	/**
	* Build the moon.
	*/
	private SimpleLightNode buildMoon() {
		// Create the moonlight which is a PointLight.
		this.moonlight = new PointLight();
		// Initialize the time is 0:00, so the moonlight color is the moon color.
		this.moonlight.setEnabled(true);
		this.moonlight.setDiffuse(new ColorRGBA(this.mColor[0], this.mColor[1], this.mColor[2], this.mColor[3]));
		this.moonlight.setAmbient(ColorRGBA.gray);
		this.moonlight.setAttenuate(false);
		this.moonlight.setShadowCaster(true);
		// Create the moon which is a LightNode.
		SimpleLightNode moon = new SimpleLightNode("Moon", this.moonlight);
		moon.setLocalTranslation(new Vector3f(0,20*this.radius,0));
		// Attach the moonlight to the astronomy LightState.
		this.astronomyLightState.attach(this.moonlight);
		// Return the moon.
		return moon;
	}

	/**
	* Build the moon's lens flare effect.
	* @return The light node contains the moon's lens flare effect.
	*/
	private Node buildMoonLensFlare() {
		// Create the moon lens flare light node.
		Node moonLensFlare = new Node("MoonLensFlare");
		moonLensFlare.setLocalTranslation(new Vector3f(0,95*this.radius/100,0));
		// Load the textures.
	    TextureState[] textureStates = new TextureState[4];
	    Texture[] texs = new Texture[4];
	    String directory = ""; //XXX Replace this with your texture file directory.
	    for(int i = 0; i < textureStates.length; i++)
	    {
	        textureStates[i] = this.renderer.createTextureState();
	        int flareIndex = i + 1;
	        String fileName = directory + "flare" + flareIndex;
	        texs[i] = TextureManager.loadTexture(fileName, Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
	        textureStates[i].setTexture(texs[i]);
	        textureStates[i].setEnabled(true);
	        textureStates[i].apply();
	    }
	    // Create the moon's lens flare effect.
	    this.moonFlareEffect = LensFlareFactory.createBasicLensFlare("LensFlareEffect", textureStates);
		// The moon's maximum lens flare intensity is 0.2.
	    this.moonFlareEffect.setIntensity(0.2f);
	    this.moonFlareEffect.updateRenderState();
		// Attach the lens flare effect to the light node.
		moonLensFlare.attachChild(this.moonFlareEffect);
		// Return the lens flare light node.
		return moonLensFlare;
	}

	/**
	* Build the rotational astronomy animation.
	* @param astronomyNode The astronomy center node.
	*/
	private void buildAstronomyAnimation(Node astronomyNode) {
		// Create the animator for the astronomy animation.
		this.astronomyAnimator = new SpatialTransformer(1);
		this.astronomyAnimator.setObject(astronomyNode, 0, -1);
	    Quaternion midNight = new Quaternion();
	    midNight.fromAngleAxis(0, new Vector3f(0,0,1));
	    this.astronomyAnimator.setRotation(0, 0, midNight);
	    Quaternion noon = new Quaternion();
	    noon.fromAngleAxis(FastMath.DEG_TO_RAD*180, new Vector3f(0,0,1));
	    this.astronomyAnimator.setRotation(0, this.cycle/2, noon);
	    Quaternion finalMidNight = new Quaternion();
	    finalMidNight.fromAngleAxis(FastMath.DEG_TO_RAD*360, new Vector3f(0,0,1));
	    this.astronomyAnimator.setRotation(0, this.cycle, finalMidNight);
	    // Prepare the animator.
	    this.astronomyAnimator.setActive(true);
	    this.astronomyAnimator.setRepeatType(Controller.RT_WRAP);
	    this.astronomyAnimator.interpolateMissing();
		// Attach the animator to the astronomy center node..
		astronomyNode.addController(this.astronomyAnimator);
	}

	/**
	* Initialize the star effect with given star texture.
	* @param starTexture The texture used for the star effect.
	* @return The star effect skybox.
	*/
	private void initializeStarEffect(Texture starTexture) {
		// Create a skybox for the star effect.
		this.starEffect = new Skybox("StarEffect", this.radius/2, this.radius/2, this.radius/2);
		this.starEffect.setTexture(Skybox.Face.North, starTexture);
		this.starEffect.setTexture(Skybox.Face.East, starTexture);
		this.starEffect.setTexture(Skybox.Face.South, starTexture);
		this.starEffect.setTexture(Skybox.Face.West, starTexture);
		this.starEffect.setTexture(Skybox.Face.Up, starTexture);
		this.starEffect.setTexture(Skybox.Face.Down, starTexture);
		this.starEffect.setLocalTranslation(new Vector3f(0, 9*this.radius/10, 0));
		// The down side of skybox should be transparent.
		this.starEffect.getFace(Skybox.Face.Down).setDefaultColor(new ColorRGBA(0,0,0,0));
		// Create the blend state.
        BlendState blend = DisplaySystem.getDisplaySystem().getRenderer().createBlendState();
        blend.setBlendEnabled(true);
        blend.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
        blend.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
        blend.setTestEnabled(true);
        blend.setTestFunction(BlendState.TestFunction.GreaterThan);
        blend.setEnabled(true);

		// Assign blend state to star effect skybox.
		this.starEffect.setRenderState(blend);
		this.starEffect.updateRenderState();
		// Preload the textures.
		this.starEffect.preloadTextures();
		// Attach the star effect to the skydome.
		this.attachChild(this.starEffect);
	}

	/**
	* Set up the essential render states.
	*/
	private void setupRenderStates() {
		// Set the modes.
		this.setModes(this);
		// Set up the zbuffer state.
	    ZBufferState zbuff = this.renderer.createZBufferState();
	    zbuff.setWritable(false);
	    zbuff.setEnabled(true);
	    zbuff.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
	    this.setRenderState(zbuff);
	    // Update the render state.
	    this.updateRenderState();
	}

	/**
	* Set the texture combine mode, render queue mode, and cull mode for the given spatial.
	* @param spatial The spatial needs to be set with the modes.
	*/
	private void setModes(Spatial spatial) {
		spatial.setTextureCombineMode(TextureCombineMode.Replace);
		spatial.setRenderQueueMode(Renderer.QUEUE_SKIP);
		spatial.setCullHint(CullHint.Never);
	}

	/**
	* Convert the given String representation of time into the animation time system.
	* @param currentTime The String representation of time in the form of <HH.mm>.
	* @return The converted time in animation time system.
	*/
	private float toAnimationTime(String currentTime) {
		// Calculate the total minutes elapsed.
		int hour = Integer.valueOf(currentTime.substring(0, currentTime.indexOf(".")));
		int minute = Integer.valueOf(currentTime.substring(currentTime.indexOf(".")+1, currentTime.length()));
		float totalMinute = hour * 60 + minute;
		// Convert the total minute into animation time system.
		float animationTime = (totalMinute/(24*60))*this.cycle;
		// Return the animation time.
		return animationTime;
	}

	/**
	* Set the sky dome time with the given String. This method is typically used to
	* synchronize the sky dome time amoung all the clients over the network.
	* @param currentTime The String representation of earth time to be set.<p>
	* Note: The form of the argument has to be <HH.mm>.
	*/
	public void setCurrentTime(String currentTime) {
		// Get the animation time based on the given earth time.
		float animationTime = this.toAnimationTime(currentTime);
		// Set the animation time.
		this.astronomyAnimator.setCurTime(animationTime);
	}

	/**
	* Retrieve the current time. Typically the minutes should not be converted if the
	* returned value is used to set a scale value.
	* @param convertMinutes True if the returned time value should be in 24 hour 60 minute system.
	* 						False if the returned time value should be in 24 hour 100 minute system.
	* @return The current time.
	*/
	public float getCurrentTime(boolean convertMinutes) {
		// Get the raw time.
		String rawTimeStr = String.valueOf(this.astronomyAnimator.getCurTime()/(this.cycle/24));
		// If argument is true, convert the minutes as well.
		if(convertMinutes)
		{
			// Convert the number before the decimal into 24 hour system.
			String hourStr = rawTimeStr.substring(0, rawTimeStr.lastIndexOf("."));
			int hour = Integer.valueOf(hourStr);
			// Convert the number after the decimal into 60 minute system.
			DecimalFormat minuteFormatter = new DecimalFormat("#.##");
			String rawMinuteStr = "0" + rawTimeStr.substring(rawTimeStr.lastIndexOf("."), rawTimeStr.length());
			String minuteStr = minuteFormatter.format(60*Float.valueOf(rawMinuteStr)/100);
			float minute = Float.valueOf(minuteStr);
			if(minute == 0.6f)
			{
				minute = 1;
			}
			// Restore and return the complete earth time.
			float rawEarthTime = hour + minute;
			if(rawEarthTime == 24)
			{
				rawEarthTime = 0.0f;
			}
			DecimalFormat timeFormatter = new DecimalFormat("##.##");
			String earthTimeStr = timeFormatter.format(rawEarthTime);
			return Float.valueOf(earthTimeStr);
		}
		// Otherwise, return raw time.
		else
		{
			return Float.valueOf(rawTimeStr);
		}
	}

	/**
	* Retrieve the astronomy LightState which contains the sunlight and moonlight.
	* @return The astronomy LightState.
	*/
	public LightState getLighting() {
		return this.astronomyLightState;
	}

	/**
	* Check if the current earth time is day time.
	* @return True if the current earth time is day time. False otherwise.
	*/
	public boolean isDayTime() {
		float currentEarthTime = this.getCurrentTime(true);
		return (currentEarthTime >= 6 && currentEarthTime < 18);
	}

	/**
	* Check if the current earth time is night time.
	* @return True if the current earth time is night time. False otherwise.
	*/
	public boolean isNightTime() {
		float currentEarthTime = this.getCurrentTime(true);
		return (currentEarthTime >= 18 || currentEarthTime < 6);
	}
}